1 using System.Collections;
2 using System.Collections.Generic;
3 using System.Linq;
4 using UnityEngine;
5 using ThreeDPool.EventHandlers;
6 using ThreeDPool.Controllers;
7 using ThreeDPool.UIControllers;
8
9 namespace ThreeDPool.Managers
10 {
11 /// <summary>
12 /// This class is a rule manager for this pool game
13 /// decides on the players playing
14 /// calculates score and decides the fate of the ball and the players after each turn
15 ///
16 /// </summary>
17 public class GameManager : Singleton<GameManager>
18 {
19 public enum GameType
20 {
21 JustCue = 1,
22 ThreeBall = 3,
23 SixBall = 6,
24 SevenBall,
25 }
26
27 public enum GameState
28 {
29 Practise = 1,
30 GetSet,
31 Play,
32 Pause,
33 Complete
34 }
35
36 // update the players you would be playing this round
37 [SerializeField]
38 private string[] _playerNames;
39
40 [SerializeField]
41 private GameType _gameType;
42
43 [SerializeField]
44 private Transform _rackTransform;
45
46 [SerializeField]
47 private CueBallController _cueBall;
48
49 [SerializeField]
50 private GameUIScreen _gameUIScreen;
51
52 // maintaining a queue here so that we dont have to worry about maintain a separate field for the player turn
53 private Queue<Player> _players = new Queue<Player>();
54
55 private List<CueBallController> _ballsPocketed;
56 private List<CueBallController> _ballsHitOut;
57 private GameState _currGameState;
58 private GameState _prevGameState;
59 private bool _ballsInstantiated;
60
61 public int NumOfBallsStriked;
62
63 public GameState CurrGameState { get { return _currGameState; } }
64 public GameState PrevGameState { get { return _prevGameState; } }
65
66 public Queue<Player> Players { get { return _players; } }
67
68 public string[] Winners;
69
70 public int NumOfTimesPlayed { private set; get; }
71
72 protected override void Start()
73 {
74 base.Start();
75
76 ChangeGameState(GameState.Practise);
77 NumOfBallsStriked = 0;
78
79 if (_playerNames != null)
80 {
81 foreach (var playerName in _playerNames)
82 {
83 var player = new Player(playerName);
84
85 _players.Enqueue(player);
86 }
87 }
88
89 // declare the ballspotted and ballshit out array
90 // consider all the balls are either potted or hitout,
91 // lets fix the array size based on game type + cueball
92 int arraySize = (int)_gameType + 1;
93 _ballsPocketed = new List<CueBallController>(arraySize);
94 _ballsHitOut = new List<CueBallController>(arraySize);
95
96 // create player uis
97 _gameUIScreen.CreatePlayerUI();
98
99 // the first player in the queue will be playing first
100
101 }
102
103 /// <summary>
104 /// This function places the ball based on the game type
105 /// There are prefabs created in a order to make the placement easy based on the selected game type
106 /// </summary>
107 private void PlaceBallBasedOnGameType()
108 {
109 // just cue doesnt place any balls in the table except for the cueball
110 if (_gameType != GameType.JustCue)
111 {
112 string rackString = "Rack";
113 Instantiate((Resources.Load(_gameType.ToString() + rackString, typeof(GameObject)) as GameObject), _rackTransform.position, _rackTransform.rotation);
114 }
115 }
116
117 private bool IsGameComplete()
118 {
119 if (_ballsPocketed.Count() == (int)_gameType)
120 return true;
121
122 return false;
123 }
124
125 private IEnumerator OnGameComplete()
126 {
127 yield return new WaitForEndOfFrame();
128
129 int winningScore = 0;
130
131 // get the highest score
132 foreach (var player in _players)
133 {
134 if (player.Score >= winningScore)
135 winningScore = player.Score;
136 }
137
138 // now that we have found the winning score, check if there is anyone else with the same score
139 Winners = _players.Where(p => p.Score == winningScore).Select(p => p.Name).ToArray();
140
141 // give enough time for the ball, cue and camera to return back to its original position
142 EventManager.Notify(typeof(GameStateEvent).Name, this, new GameStateEvent() { GameState = GameStateEvent.State.Complete });
143 }
144
145 private void SetNewPlayerTurn()
146 {
147 // next player takes the chance
148 Player player = _players.Dequeue();
149 _players.Enqueue(player);
150
151 // get the player on peek to diplay the turn
152 Player newPlayer = _players.Peek();
153 EventManager.Notify(typeof(GameStateEvent).Name, this, new GameStateEvent() { CurrPlayer = newPlayer.Name });
154 }
155
156 private void CalculateThePointAndNextTurn()
157 {
158 // now that all the ball are stationary, lets decide on next state of game
159 // check the balls pocketed list, first for the cueball and then to the count
160 // if there is a cue ball in the list then the current player looses a point, and the cue ball is placed in the table
161 // else then the score is count for the current player
162 // then check if there are any ball in the floor
163 // if there is any they get placed in their respective position
164 Player currPlayer = _players.Peek();
165
166 // check if the player has striked the ball
167 if (currPlayer.HasStrikedBall)
168 {
169 CueBallController whiteBall = _ballsPocketed.FirstOrDefault(b => b.BallType == CueBallController.CueBallType.White);
170 if (whiteBall != null)
171 {
172 // player looses score
173 currPlayer.CalculateScore(-1);
174
175 // remove the white ball from pocket
176 _ballsPocketed.Remove(whiteBall);
177
178 // set all pocketed balls to true, as the ball pocketed along with the white ball is considered already pocketed
179 _ballsPocketed.ForEach(b => b.IsPocketedInPrevTurn = true);
180
181 // place the cue ball back in the table
182 whiteBall.PlaceBallInInitialPos();
183
184 SetNewPlayerTurn();
185 }
186 else
187 {
188 if (_ballsPocketed.Count() > 0)
189 {
190 // get the balls that are currently pockted, the currently pocketed ball will have a value set to false
191 var ballsCurrentlyPocketed = _ballsPocketed.Where(b => b.IsPocketedInPrevTurn == false);
192 Debug.Log("Balls Currently Pocketed" + ballsCurrentlyPocketed.Count());
193 if (ballsCurrentlyPocketed.Count() > 0)
194 {
195 // count for the number of balls pocketed, and increment the player score
196 currPlayer.CalculateScore(ballsCurrentlyPocketed.Count());
197
198 // set all pocketed balls to true
199 _ballsPocketed.ForEach(b => b.IsPocketedInPrevTurn = true);
200
201 // player continues to play
202 }
203 else
204 {
205 SetNewPlayerTurn();
206 }
207 }
208 else
209 {
210 SetNewPlayerTurn();
211 }
212 }
213
214 // place these balls back in the pool table
215 foreach (var ballHitOut in _ballsHitOut)
216 ballHitOut.PlaceBallInInitialPos();
217 }
218
219 // clear up every information
220 _ballsHitOut.Clear();
221
222 // reset players state
223 foreach (var player in _players)
224 {
225 // checking if the current player in iteration is the first player and setting the state accordingly
226 // only the first player in the queue is in playing state
227 // note: state is set based on the condition
228 player.SetPlayingState((player == _players.Peek()));
229 }
230
231 // check if all balls in the game are pocketed
232 if (IsGameComplete())
233 StartCoroutine(OnGameComplete());
234 else
235 EventManager.Notify(typeof(CueBallActionEvent).Name, this, new CueBallActionEvent() { State = CueBallActionEvent.States.Stationary });
236 }
237
238 public void ChangeGameState(GameState newGameState)
239 {
240 // making sure that the prev game state is actually the prev game state
241 if(newGameState != _currGameState)
242 {
243 _prevGameState = _currGameState;
244 _currGameState = newGameState;
245 }
246 }
247
248 public void OnGetSet()
249 {
250 ChangeGameState(GameState.GetSet);
251 }
252
253 public void OnPlay()
254 {
255 // make sure we start with clear list
256 _ballsHitOut.Clear();
257 _ballsPocketed.Clear();
258
259 NumOfBallsStriked = 0;
260
261 NumOfTimesPlayed++;
262
263 foreach (var player in _players)
264 player.ResetScore();
265
266 ChangeGameState(GameState.Play);
267
268 // place the cue ball in position
269 _cueBall.PlaceBallInInitialPos();
270
271 if (!_ballsInstantiated)
272 {
273 // place the ball in the rack position
274 PlaceBallBasedOnGameType();
275
276 _ballsInstantiated = true;
277 }
278 }
279
280 public void OnPaused()
281 {
282 ChangeGameState(GameState.Pause);
283 }
284
285 public void OnContinue()
286 {
287 ChangeGameState(GameState.Play);
288 }
289
290 // this function will be called by cueball when they come to rest after the shot is taken
291 public void ReadyForNextRound()
292 {
293 // lets allow the player to take some shot while the game is not started yet
294 if (CurrGameState == GameState.Practise)
295 {
296 // white ball might be pocketed or hit out
297 // since there is only white before the Play state dont have to check for the ball type, just see which array has the ball object
298 _cueBall.PlaceBallInPosWhilePractise();
299 }
300 // pause is added to just make sure its ready for next round
301 else if(CurrGameState == GameState.Play || CurrGameState == GameState.Pause)
302 {
303 NumOfBallsStriked--;
304
305 // all the balls in the pool table are now stationary, let the game continue
306 if (NumOfBallsStriked == 0)
307 CalculateThePointAndNextTurn();
308 }
309 else
310 {
311 // do nothing
312 }
313 }
314
315 public void AddToBallPocketedList(CueBallController ball)
316 {
317 // making sure we are not adding a ball multiple times
318 if (!_ballsPocketed.Contains(ball))
319 _ballsPocketed.Add(ball);
320 }
321
322 public void AddToBallHitOutList(CueBallController ball)
323 {
324 // these balls will be back on pool table
325 // check if the ball is already potted too and then fell on floor
326 if (!_ballsHitOut.Contains(ball) && !_ballsPocketed.Contains(ball))
327 _ballsHitOut.Add(ball);
328 }
329 }
330 }